package com.yuanhua.sms.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.yuanhua.sms.entity.ReturnMessage;
import com.yuanhua.sms.excption.BusinessException;
import org.apache.commons.lang3.RandomUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

@Service
public class CacheSmsService {
    private static final Logger logger = LoggerFactory.getLogger(CacheSmsService.class);

    private static final String PREFIX = "sms:";
    @Autowired
    private SmsService smsService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 验证默认有效期
     */
    private static final int DEFAULT_EXPIRE = 5;
    private static final int CODE_MAX_LENGTH = 6;
    private static final int CODE_MAX_COUNT = 10;
    private static final int PHONE_EXPIRE = 1;

    /**
     * 发送短信，短信具有过期时间；不能重复
     * 一个电话只能发送MAX_COUNT 次
     *
     * @param phone
     * @param code
     * @param result
     * @return
     * @Async 启动异步线程池处理任务
     */
    @Async
    public Future<Boolean> send(String phone, String code, ReturnMessage<Object> result) {

        //否则可以发送,判断前端是否传递code，没有则生成
        if (!StringUtils.hasText(code)) {
            code = String.valueOf(RandomUtils.nextInt(0, 6));
        }
        String send = smsService.send(phone, code, DEFAULT_EXPIRE);
        JSONObject data = JSONObject.parseObject(send);
        String message = data.getString("Message");
        String resultCode = data.getString("Code");
        if (!("OK".equals(message) && "OK".equals(resultCode))) {
            result.setData(data);
            //自定义异常，短信发送失败
            throw new BusinessException("短信发送失败，网络异常");
        }
        result.setData(data);
        return new AsyncResult<>(true);
    }


    /**
     * 根据手机号和验证码生成缓存KEY
     *
     * @param phone
     * @param code
     * @return
     */
    private String getKey(String phone, String code) {
        return PREFIX + phone + "-" + code;
    }

    /**
     * 方案一，使用线程池处理一般并发的注册登录需要发送的验证码；
     * 方案二，使用redis消息队列处理：只负责将号码和内容放入list中，使用一个线程监听list，
     *
     * @param phone
     * @param code
     * @return
     */
    public ReturnMessage sendMessage(String phone, String code) {
        ReturnMessage<Object> result = new ReturnMessage<>();
        String lockKey = "Lock_"+phone;
        try {
            //需要加一层缓存，判断验证码是否过期；限制重复发送
            boolean hasKey = redisTemplate.hasKey(getKey(phone, code));
            //存在key并且还没过期，不让继续发送
            if (hasKey) {
                throw new BusinessException("验证码任在有效期中，有效期为" + DEFAULT_EXPIRE + "分钟");
            }
            //加一个redis分布式锁防止同一个号码多个线程同时进入这段代码发送短信；导致redis拦截无效
            Boolean nx = redisTemplate.boundValueOps(lockKey).setIfAbsent("NX", 1, TimeUnit.SECONDS);
            //枷锁失败，说明有线程往下执行了，所以直接抛出验证码还有效的异常
            if (Boolean.FALSE.equals(nx)){
                throw new BusinessException("验证码任在有效期中，有效期为" + DEFAULT_EXPIRE + "分钟，请勿重复请求");
            }
            String phoneKey = PREFIX + phone;
            if (redisTemplate.hasKey(phoneKey)) {
                String count = redisTemplate.boundValueOps(phoneKey).get();
                //今日次数用完
                if (Integer.parseInt(count) <= 0)
                    throw new BusinessException("今日次数已用完，次数为" + CODE_MAX_COUNT + "次，请明天尝试");
            } else {
                //手机号放入缓存,并设置过期时间为一天
                redisTemplate.boundValueOps(phoneKey).set(String.valueOf(CODE_MAX_COUNT), PHONE_EXPIRE, TimeUnit.DAYS);
            }
            //循环判断任务是否执行完毕、不需要返回信息，否则无法处理高并发请求；
            Future<Boolean> send = send(phone, code, result);
            while (true) {
                if (send.isDone()) {
                    result.setStatus(200);
                    if (send.get()) {
                        //放入缓存，DEFAULT_EXPIRE分钟过期；
                        redisTemplate.boundValueOps(getKey(phone, code)).set("success", DEFAULT_EXPIRE, TimeUnit.MINUTES);
                        //发送成功，减少次数
                        redisTemplate.boundValueOps(phoneKey).decrement(1);
                        redisTemplate.delete(lockKey);
                    }
                    break;
                }
            }
        } catch (BusinessException exception) {
            result.setStatus(exception.getCode());
            result.setMessage(exception.getMsg());
        } catch (Exception e) {
            logger.info("[SmsCacheService] error: {}", e.toString());
            throw new BusinessException(e, "缓存出现异常");
        }
        return result;
    }

    /**
     * 验证手机号
     *
     * @param phone
     * @param code
     */
    public void verify(String phone, String code) {

        if (Boolean.TRUE.equals(redisTemplate.hasKey(getKey(phone, code)))) {
            logger.info("[CacheSmsService] verity 验证成功，手机号：{},验证码:{}", phone, code);
            return;
        }
        throw new BusinessException("手机号验证失败");
    }
}
